/*
 * Copyright 2009-2010 the original author or authors.
 *
 * Licensed under the Apache license, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.internna.iwebjtracker.services;

import java.util.Map;
import java.util.List;
import java.util.HashMap;
import javax.annotation.security.RolesAllowed;
import org.internna.iwebjtracker.issue.model.Issue.Link;
import org.internna.iwebjtracker.issue.model.Issue.ResolutionStatus;
import org.internna.iwebmvc.dao.DAO;
import org.internna.iwebmvc.model.Document;
import org.internna.iwebmvc.utils.Assert;
import org.internna.iwebmvc.utils.CollectionUtils;
import org.internna.iwebjtracker.model.TrackerUser;
import org.internna.iwebjtracker.model.IssueComment;
import org.internna.iwebjtracker.model.DefaultIssue;
import org.directwebremoting.annotations.RemoteProxy;
import org.internna.iwebjtracker.model.DefaultModule;
import org.directwebremoting.annotations.RemoteMethod;
import org.internna.iwebjtracker.issue.model.Issue;
import org.internna.iwebjtracker.model.DefaultProject;
import org.internna.iwebjtracker.model.DefaultVersion;
import org.internna.iwebjtracker.project.validation.Errors;
import org.internna.iwebmvc.model.DomainEntity;
import org.internna.iwebmvc.model.MultiDocument;
import org.internna.iwebmvc.model.UUID;
import org.springframework.context.MessageSource;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.transaction.annotation.Transactional;

import static org.internna.iwebmvc.utils.StringUtils.hasText;
import static org.internna.iwebmvc.model.security.RoleImpl.ROLE_USER;
import static org.internna.iwebjtracker.boot.tasks.CreateRolesStartupTask.ROLE_DEVELOPER;

/**
 *
 *
 * @author Jose Noheda
 * @since 1.0
 */
@RemoteProxy(name = "RemoteIssueManager")
public final class RemoteIssueManager extends AbstractIssueManager<DefaultModule, DefaultVersion, TrackerUser, DefaultProject, DefaultIssue, IssueComment> {

    private DAO dao;
    private TrackerUserManager userManager;

    /**
     * Constructor injection
     *
     * @param messageSource any
     */
    public RemoteIssueManager(MessageSource messageSource) {
        super(messageSource);
    }

    /**
     * Spring (REQUIRED) injection point.
     *
     * @param userManager a not null instance
     */
    @Required public void setUserManager(TrackerUserManager userManager) {
        Assert.notNull(userManager);
        this.userManager = userManager;
    }

    /**
     * Spring (REQUIRED) injection point.
     *
     * @param dao a not null implementation
     */
    @Required public void setDao(DAO dao) {
        Assert.notNull(dao);
        this.dao = dao;
    }

    /**
     * Votes the issue provided using the current logged in user.
     *
     * @param issue any
     * @return r
     */
    @RemoteMethod
    @Transactional
    @RolesAllowed(ROLE_USER)
    @Override public boolean vote(DefaultIssue issue) {
        boolean vote = false;
        DefaultIssue loaded = loadIssue(issue);
        if (loaded != null) {
            vote = loaded.vote(userManager.getCurrentTrackerUser());
            if (vote) {
                persistIssue(issue);
            }
        }
        return vote;
    }

    @RemoteMethod
    @Transactional
    @RolesAllowed(ROLE_USER)
    @Override public boolean watch(DefaultIssue issue) {
        boolean watch = false;
        DefaultIssue loaded = loadIssue(issue);
        if (loaded != null) {
            watch = loaded.watch(userManager.getCurrentTrackerUser());
            if (watch) {
                persistIssue(issue);
            }
        }
        return watch;
    }

    /**
     * Allows a logged in user to comment an issue (and optionally respond to another comment).
     *
     * @param issue any
     * @param parentComment any
     * @param chat any
     * @return
     */
    @RemoteMethod
    @Transactional
    @RolesAllowed(ROLE_USER)
    @Override public void comment(DefaultIssue issue, IssueComment parentComment, String chat) {
        DefaultIssue loaded = loadIssue(issue);
        if ((loaded != null) && (hasText(chat))) {
            IssueComment data = new IssueComment();
            data.setAuthor(userManager.getCurrentTrackerUser());
            data.setComment(chat);
            if ((parentComment != null) && (parentComment.getId() != null)) {
                data.setResponseTo(dao.find(IssueComment.class, parentComment.getId()));
            }
            data.setIssue(loaded);
            dao.create(data);
        }
    }

    /**
     * Loads issue and document, removes the temporal flag and delegates logic.
     *
     * @param issue any (if null no action performed)
     * @param attachment (if null no action performed)
     * @return null unless the doc was successfully added
     */
    @RemoteMethod
    @Transactional
    @RolesAllowed(ROLE_USER)
    @Override public Document addAttachment(DefaultIssue issue, Document attachment) {
        DefaultIssue loaded = loadIssue(issue);
        if ((loaded != null) && (attachment != null) && (attachment.getId() != null)) {
            Document doc = dao.find(Document.class, attachment.getId());
            doc.setTemporal(false);
            MultiDocument md = loaded.addAttachment(userManager.getCurrentTrackerUser(), doc);
            if (md != null) {
                persistIssue(loaded);
                return doc;
            }
        }
        return null;
    }

    /**
     * Persists the issue with the current user as the assignee.
     *
     * @param issue any
     * @return the translated status
     */
    @RemoteMethod
    @Transactional
    @RolesAllowed(ROLE_DEVELOPER)
    @Override public String assignToMe(DefaultIssue issue) {
        DefaultIssue loaded = loadIssue(issue);
        if ((loaded != null) && (!Issue.Status.CLOSED.equals(loaded.getStatus()))) {
            TrackerUser user = userManager.getCurrentTrackerUser();
            if ((user != null) && (loaded.getParentProject().isDeveloper(user))) {
                loaded.setAssignee(user);
                if (user.equals(loaded.getAssignee())) {
                    persistIssue(issue);
                    return resolve("Status." + loaded.getStatus().toString());
                }
            }
        }
        return null;
    }

    /**
     * Checks if the current user is a developer in the project and persist
     * the issue in case
     *
     * @param origin a non null valid issue
     * @param target a non null valid issue
     * @param type anon null link type
     * @return any
     */
    @Override protected Errors linkWithUser(DefaultIssue origin, DefaultIssue target, Link type) {
        Errors error = Errors.PROJECT_NOT_A_DEVELOPER;
        TrackerUser user = userManager.getCurrentTrackerUser();
        if ((user != null) && (origin.getParentProject().isDeveloper(user))) {
            error = origin.link(userManager.getCurrentTrackerUser(), target, type);
            if (error == null) {
                persistIssue(origin);
            }
        }
        return error;
    }

    /**
     * Gets the current user, delegates the closing logic and updates persistent data.
     *
     * @param issue a non null non closed issue
     * @param status any
     * @param comment any
     * @return any
     */
    @Override protected Errors proceedWithClose(DefaultIssue issue, ResolutionStatus status, String comment) {
        TrackerUser user = userManager.getCurrentTrackerUser();
        if ((user != null) && (issue.getParentProject().isDeveloper(user)) && (issue.close(user, status))) {
            if (hasText(comment)) {
                comment(issue, null, comment);
            }
            dao.update(issue);
            return null;
        }
        return Errors.PROJECT_NOT_A_DEVELOPER;
    }

    /**
     * Gets issue data from database.
     *
     * @param issue any (null avoids loading operation)
     * @return the loaded issue if existed, null otherwise
     */
    @Override protected DefaultIssue loadIssue(DefaultIssue issue) {
        return issue != null ? load(DefaultIssue.class, issue.getId()) : null;
    }

    /**
     * Gets a version from the database.
     *
     * @param version any
     * @return the loaded issue if existed, null otherwise
     */
    @Override protected DefaultVersion loadVersion(DefaultVersion version) {
        return version != null ? load(DefaultVersion.class, version.getId()) : null;
    }

    private <D extends DomainEntity> D load(Class<D> clazz, UUID id) {
        return id != null ? dao.find(clazz, id) : null;
    }

    /**
     * Creates new issues in the database.
     *
     * @param issue any
     */
    @Override protected void persistIssue(DefaultIssue issue) {
        if (issue != null) {
            if (issue.getId() == null) {
                dao.create(parse(issue));
            } else {
                dao.update(issue);
            }
        }
    }

    /**
     * Refreshes values from database for remoted persistent entities.
     *
     * @param issue any
     * @return the issue provided
     */
    protected DefaultIssue parse(DefaultIssue issue) {
        if (issue != null) {
            issue.setAlias(obtainCurrentAlias(issue));
            issue.setCreator(userManager.getCurrentTrackerUser());
            issue.setParentProject(dao.find(DefaultProject.class, issue.getParentProject().getId()));
            issue.setParentModule(dao.find(DefaultModule.class, issue.getParentModule().getId()));
            issue.setAffectedVersion(dao.find(DefaultVersion.class, issue.getAffectedVersion().getId()));
        }
        return issue;
    }

    /**
     * Generates a new unique (for this project) incremental issue number.
     *
     * @param issue any
     * @return a number >= -1
     */
    protected int obtainCurrentAlias(DefaultIssue issue) {
        Integer alias = -1;
        if ((issue != null) && (issue.getParentProject() != null)) {
            Map<String, Object> params = new HashMap<String, Object>(1);
            params.put("project", issue.getParentProject().getId());
            List<Object> max = dao.executeQuery(DefaultIssue.FIND_BY_CURRENT_ALIAS, 0, 1, params);
            if (CollectionUtils.isNotEmpty(max)) {
                alias = max.get(0) == null ? 1 : (Integer) max.get(0);
            }
        }
        return alias;
    }

}
